#!/usr/bin/ruby
require 'gtkglext'
require 'alg3d'
## -----------------------------------------------------------------------------
## A generic class for OpenGL application functions.
## -----------------------------------------------------------------------------
$x_rot = 0
$y_rot = 0
$zoom = -10
$buttons_down = 0
$last_pos = [0,0]
class GLRender 
  include GLIT
  attr_reader :fov

  def initialize(fov = 90.0)
    @camera_quat = Quat.new
    @fov = fov
  end

  ## Initialise OpenGL state for 3D rendering.
  def init()
    GL.ShadeModel(GL::SMOOTH)
    GL.Enable(GL::DEPTH_TEST)
    GL.DepthFunc(GL::LEQUAL)
    GL.ClearColor(0.0, 0.0, 0.0, 0.0)
    GL.Hint(GL::PERSPECTIVE_CORRECTION_HINT, GL::NICEST)
    true
  end

  ## Resize OpenGL viewport.
  def resize(width, height)
    GL.Viewport(0, 0, width, height)

    GL.MatrixMode(GL::PROJECTION)
    GL.LoadIdentity()
    GLU.Perspective(@fov, width.to_f() / height.to_f(), 0.1, 1024.0)
    
    GL.MatrixMode(GL::MODELVIEW)
    GL.LoadIdentity()
  end

  ## Render OpenGL scene.
  def draw()


    GL.Clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT)
    GL.LoadIdentity()
    
    ## Scene view translation. ---------------------------------------------->>>
    key_quat = Quat.rpy2q($y_rot, $x_rot, 0.0)  # New Quaternion representing any incremental rotation from mouse
    @camera_quat *= key_quat                    # Multiply it into the current camera rotation
    $x_rot = 0 # reset mouse input to zero
    $y_rot = 0
    @camera_quat.normalize! # make sure it's all normal
    GL.Translate(0.0, 0.0, $zoom)
    GL.Rotate(@camera_quat.angle_rads*(180/Math::PI), 
              @camera_quat.axis[0], 
              @camera_quat.axis[1], 
              @camera_quat.axis[2])
    ## -------------------------------------------------------------------------

    ## Scene Rendering Code ------------------------------------------------->>>
    GL.Begin(GL::TRIANGLES)
     GL.Color3f(0, 0.5, 1)
      GL.Vertex3f(-1, -1, -1)
      GL.Vertex3f(1, 1, -1)
      GL.Vertex3f(1, -1, -1)

      GL.Vertex3f(-1, -1, -1)
      GL.Vertex3f(-1, 1, -1)
      GL.Vertex3f(1, 1, -1)
     GL.Color3f(0, 1, 0.5)
      GL.Vertex3f(1, -1, 1)
      GL.Vertex3f(1, 1, 1)
      GL.Vertex3f(1, 1, -1)

      GL.Vertex3f(1, -1, 1)
      GL.Vertex3f(1, 1, -1)
      GL.Vertex3f(1, -1, -1)
     GL.Color3f(0.5, 1, 0)
      GL.Vertex3f(-1, -1, 1)
      GL.Vertex3f(-1, 1, 1)
      GL.Vertex3f(-1, 1, -1)

      GL.Vertex3f(-1, -1, 1)
      GL.Vertex3f(-1, 1, -1)
      GL.Vertex3f(-1, -1, -1)

     GL.Color3f(0.5, 0, 1)
      GL.Vertex3f(-1, -1, 1)
      GL.Vertex3f(-1, 1, 1)
      GL.Vertex3f(1, 1, 1)

      GL.Vertex3f(-1, -1, 1)
      GL.Vertex3f(1, 1, 1)
      GL.Vertex3f(1, -1, 1)
     GL.Color3f(1, 0, 0.5)
      GL.Vertex3f(-1, 1, 1)
      GL.Vertex3f(-1, 1, -1)
      GL.Vertex3f(1, 1, -1)

      GL.Vertex3f(-1, 1, 1)
      GL.Vertex3f(1, 1, -1)
      GL.Vertex3f(1, 1, 1)
     GL.Color3f(1, 0.5, 0)
      GL.Vertex3f(-1, -1, 1)
      GL.Vertex3f(1, -1, 1)
      GL.Vertex3f(1, -1, -1)

      GL.Vertex3f(-1, -1, 1)
      GL.Vertex3f(1, -1, -1)
      GL.Vertex3f(-1, -1, -1)
    GL.End()
    ## -------------------------------------------------------------------------
    GL.Flush()
  end
end

## -----------------------------------------------------------------------------
## A GtkDrawingArea widget with OpenGL rendering capabilities.
## -----------------------------------------------------------------------------
class GLDrawingArea < Gtk::DrawingArea
  def initialize(width, height, fov, gl_config)
    super()
    set_size_request(width, height)
    set_gl_capability(gl_config)

    ## Create an OpenGL renderer instance.
    @render = GLRender.new(fov)

    ## Signal handler for drawing area initialisation.
    signal_connect_after("realize") do
      gl_begin() { @render.init() }
    end

    ## Signal handler for drawing area reshapes.
    signal_connect_after("configure_event") do
      gl_begin() { @render.resize(allocation.width, allocation.height) }
    end

    ## Signal handler for drawing area expose events.
    signal_connect_after("expose_event") do
      gl_begin() do
        @render.draw()
      end

      gl_drawable.swap_buffers() if gl_drawable.double_buffered?
    end

    ## Add mouse button press/release signal event handlers
    add_events(Gdk::Event::POINTER_MOTION_MASK |
               Gdk::Event::BUTTON_PRESS_MASK |
               Gdk::Event::BUTTON_RELEASE_MASK)

    signal_connect_after("button_press_event") { |w, e| button_press_event(w, e) }
    signal_connect_after("button_release_event") { button_release_event() }
    signal_connect_after("scroll_event") { |w, e, d| scroll_event(w, e, d) }
    signal_connect_after("motion_notify_event") { |w, e, d| drag_event(w, e, d) }
  end

  def gl_begin()
    gl_drawable.gl_begin(gl_context) { yield }
  end

  def button_press_event(w, e)
    $buttons_down += 1
      $last_pos[0] = e.x
      $last_pos[1] = e.y
    #puts "button_press_event"
    @render.draw()
    gl_drawable.swap_buffers() if gl_drawable.double_buffered?
    true
  end
 def scroll_event(w, e, d)
    $zoom -= 0.25 if (e.direction == Gdk::EventScroll::UP)
    $zoom += 0.25 if (e.direction == Gdk::EventScroll::DOWN)
    #puts "scroll_event: \n\t#{w}\n\t#{e.direction}\n\t#{e}"

    @render.draw()
    gl_drawable.swap_buffers() if gl_drawable.double_buffered?
    #puts $zoom
    true
  end
  def drag_event(w, e, d)
    if ($buttons_down > 0)
      #puts "drag_event: \n\t#{w}\n\t#{e}\n\t#{d}" 
      $x_rot = ($last_pos[0] - e.x) / -100.0
      $y_rot = ($last_pos[1] - e.y) / -100.0
      @render.draw()
      gl_drawable.swap_buffers() if gl_drawable.double_buffered?
    end
    $last_pos[0] = e.x
    $last_pos[1] = e.y
    true
  end

  def button_release_event()
    $buttons_down -= 1
    #puts "button_release_event"
    true
  end
end

## -----------------------------------------------------------------------------
## Application GUI Main Window Class
## -----------------------------------------------------------------------------
class AppWindow < Gtk::Window
  attr_reader :gl_area

  def initialize(title, width, height, fov, gl_config)
    super(title)

    ## Connect signals to handle keyboard user input.
    signal_connect_after("key_press_event") { key_press_event() }
    signal_connect_after("key_release_event") { key_release_event() }

    ## Create our GL drawing area widget.
    @gl_area = GLDrawingArea.new(width, height, fov, gl_config)
    column = Gtk::VBox.new( false, 0 )

    topbox = Gtk::HBox.new( false, 0 )

    $status = Gtk::ProgressBar.new()
    #status.pulse()
    topbox.pack_start( $status, false, true, 5 )
    $status.show

    column.pack_start( topbox, false, true, 5 )
    topbox.show

    # Another new separator
    separator = Gtk::HSeparator.new
    column.pack_start( separator, false, true, 5 )
    separator.show

    column.pack_start( @gl_area, false, false, 5 )
    add(column)
    show_all()
  end

  def key_press_event()
    puts "key_press_event"
    true
  end

  def key_release_event()
    puts "key_release_event"
    true
  end
end

## -----------------------------------------------------------------------------
## Application Class
## -----------------------------------------------------------------------------
class GtkGLApp
  attr_reader :window

  def initialize(title, width, height, bpp)
    ## Initialise Gtk[GLExt] library.
    Gtk.init()
    Gtk::GL.init()

    ## Obtain a valid OpenGL context configuration.
    gl_config = Gdk::GLConfig.new(Gdk::GLConfig::MODE_DEPTH |
                                  Gdk::GLConfig::MODE_DOUBLE |
                                  Gdk::GLConfig::MODE_RGBA)

    ## Create application main interface window.
    @window = AppWindow.new(title, width, height, 40.0, gl_config)

    @window.signal_connect_after("delete_event") do
      puts "Closing application."
      @window.destroy()
      Gtk.main_quit()
    end
  end

  def main()
    Gtk.main()  ## Enter into Gtk's main event processing loop.
    true
  end
end

## -----------------------------------------------------------------------------
## Application Entry Point
## -----------------------------------------------------------------------------
if $0 == __FILE__
  ## Create new application context.
  context = GtkGLApp.new("Gtk GL Application", 512, 512, 16)
  context.main()  ## Enter into the main execution loop.
end

